技巧13 将一个系统拆成微服务容器

我们已经探讨了该如何把一个容器用作一个单体实体(像传统的服务器那样),并且阐明了这会是一个快速将一个系统架构迁移到Docker上的好方法。然而,在Docker的世界里,公认的最佳实践是尽可能多地把系统拆分开,直到在每个容器上都只运行一个“服务”,并且所有容器都通过网络互相连通。

使用一个容器一个服务的主要原因在于可以更容易通过单一职责原则(single responsibility principle)实现关注点分离(separation of concerns)。如果用户的容器执行的是单一任务,那么可以很方便地把该容器应用到从开发、测试到生产的整个软件开发生命周期里,而无须太担心它与其他组件的交互问题。这就使软件项目可以更敏捷地交付并且具备更好的扩展性。但是,它的确带来了一些管理上的负担,因此,最好思量一下在自己的用例场景下这样做是否真的值得。

暂且不论哪种方案更适合,最佳实践方法至少拥有一个明显的优势——正如所见,在使用Dockerfile时实验和重新构建都比前一套方案快上不少。

问题

想要将应用程序拆分为各个单独的且更易于管理的服务。

解决方案

为每个单独的服务进程对应创建一个容器。

正如我们之前提到的那样,在Docker社区里,在应当怎样严格遵守“一个容器一个服务”的规则方面还存在一些争议,这其中部分源自在定义方面的一些异议——它是一个单个的进程,还是可以结合在一起共同满足一个需求的一组进程?最终,它往往会被归结为这样一种说法,即赋予从头开始重新设计系统的能力,微服务也许会是大多数人的选择。但是有时候实用主义可能会战胜理想主义——当为组织评估Docker时,为了能够让Docker尽可能更快、更容易地用起来,我们发现自己在当时的处境下只能选择单体这条路。

让我们一起来看看在Docker内部运行单体应用的其中一个具体弊端。首先,代码清单3-7中展示了如何构建一个拥有数据库、应用程序以及Web服务器的单体应用。

注意

这些例子只是用于解释说明的目的,并且已经被相应简化。尝试直接运行它们不一定能正常工作。

代码清单3-7 配置一个简单的PostgreSQL、NodeJS和Nginx应用

FROM ubuntu:14.04
RUN apt-get update && apt-get install postgresql nodejs npm nginx
WORKDIR /opt
COPY . /opt/                                  # {*}
RUN service postgresql start && \
    cat db/schema.sql | psql && \
    service postgresql stop
RUN cd app && npm install
RUN cp conf/mysite /etc/nginx/sites-available/ && \
    cd /etc/nginx/sites-enabled && \
    ln -s ../sites-available/mysite

提示

每条Dockerfile命令在执行时都会在之前的镜像基础上创建一个新的层,但是在 RUN 语句里使用 && 能够有效确保这些命令作为一条命令执行。这一点非常有用,因为它可以保持镜像不至于太大。如果以这种方式执行一个软件包更新的命令,如 apt-get update 这样的安装命令,用户便能够确保无论软件包是何时安装的,它们都将来源于一个已经更新过的包缓存。

前面的例子是一个简单的概念版的Dockerfile,它会在容器里安装一切需要的软件,并随后配置好数据库、应用程序和Web服务器。但是,如果想快速地重新构建容器就有问题了——仓库下的任何文件的任意变更均会造成一切事物从 {*} 开始重新构建,因为这种情况下无法复用之前的缓存。如果存在一些执行较慢的步骤(数据库的创建或 npm install ),可能就得在容器重新构建的时候等待一段时间。

这个问题的解决方案便是将 COPY . /opt/ 指令拆到应用(数据库、应用程序和Web配置)的各个部分,如代码清单3-8所示。

代码清单3-8 一个单体应用的Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install postgresql nodejs npm nginx
WORKDIR /opt
COPY db /opt/db                                     -+
RUN service postgresql start && \                    |- 设置db
    cat db/schema.sql | psql && \                    |
    service postgresql stop                         -+
COPY app /opt/app                                   -+
RUN cd app && npm install                            |- 设置app
RUN cd app && ./minify_static.sh                    -+
COPY conf /opt/conf                                 -+
RUN cp conf/mysite /etc/nginx/sites-available/ && \  +
    cd /etc/nginx/sites-enabled && \                 |- 设置web
    ln -s ../sites-available/mysite                 -+

在前面的代码里, COPY 命令被拆成两条单独的指令。由于可以利用缓存复用在修改代码之前未经变更的交付文件,这就意味着数据库不会在每次代码更改的时候都重新构建。但是,由于缓存功能是相当简单粗糙的,容器仍然不得不在每次对schema脚本做出更改时完全地重新构建。解决这一问题的唯一途径便是抛弃原有顺序配置的步骤,创建多份Dockerfile,内容如代码清单3-9到代码清单3-11所示。

代码清单3-9 postgres服务的Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install postgresql
WORKDIR /opt
COPY db /opt/db
RUN service postgresql start && \
    cat db/schema.sql | psql && \
    service postgresql stop

代码清单3-10 nodejs服务的Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install nodejs npm
WORKDIR /opt
COPY app /opt/app
RUN cd app && npm install
RUN cd app && ./minify_static.sh

代码清单3-11 nginx服务的Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install nginx
WORKDIR /opt
COPY conf /opt/conf
RUN cp conf/mysite /etc/nginx/sites-available/ && \
    cd /etc/nginx/sites-enabled && \
    ln -s ../sites-available/mysite

每当 dbappconf 文件夹下的内容有一个发生变化,将会只有一个容器需要被重新构建。当有3个以上容器或者有一些对时间敏感的配置步骤时,这样做会特别有用。只要花上一些心思,在每个步骤中添加最低限度的所需文件,结果便是可以最大程度地利用Dockerfile的缓存机制。

在应用的Dockerfile(见代码清单3-10)里, npm install 操作定义在了一个单独的文件package.json里,因此我们可以通过修改我们的Dockerfile以利用dockefile本身的镜像层缓存机制,并且只需要在必要的时候才去重新构建缓慢的 npm install ,如代码清单3-12所示。

代码清单3-12 一份更快的nodejs服务的Dockerfile

FROM ubuntu:14.04
RUN apt-get update && apt-get install nodejs npm
WORKDIR /opt
COPY app/package.json /opt/app/package.json
RUN cd app && npm install
COPY app /opt/app
RUN cd app && ./minify_static.sh

之前只有1个Dockerfile,而现如今我们得到了3个分离的、相对独立的Dockerfile。

讨论

但是,天下没有免费的午餐——我们必须将一个简单的Dockerfile转换为多个重复的Dockerfile。 我们可以通过添加另外一个Dockerfile当作自己的基础镜像来解决部分问题,但是其他这样重复的情况并不少见。此外,现在启动镜像时又会冒出一些新的复杂性——除在 EXPOSE 步骤让一些合适的端口可以用于链接以及修改Postgres配置外,我们还需要确保在自己每次启动时链接到各个容器。幸运的是,已经有这样的一款工具,它叫作Docker Compose,我们将会在技巧76里介绍它。

到目前为止,在本节中我们已经完成了将虚拟机转换为Docker镜像,运行一个类宿主机的容器,并能够将单体应用块拆分成单独的几个Docker镜像。

如果在阅读本书之后,你仍希望从容器中运行多个进程,可以使用特定工具来帮助完成这项操作。其中一个便是Supervisord,我们将在技巧14中介绍。

results matching ""

    No results matching ""